最近有使用springboot 内嵌容器undertow,并开启了access-log的动能,发现pattern
stc=%s snt=%B dur=%D中的%D没有生效,发现是在SmartInitializingSingleton 阶段进行设置的。
SmartInitializingSingleton的定义
public interface SmartInitializingSingleton {
void afterSingletonsInstantiated();
}
SpringApplication创建ApplicationContext时,会调用refreshContext(context);来实现BeanDefinition的初始化,和事件监听的绑定。其中会有如下的调用链条:
AbstractApplicationContext#refresh()
->AbstractApplicationContext#finishBeanFactoryInitialization()
->org.springframework.beans.factory.surpoort.DefaultListableBeanFactory#preInstantiateSingletons()
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
然后
// Trigger post-initialization callback for all applicable beans...
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
}
else {
// 这里开始新世界
smartSingleton.afterSingletonsInstantiated();
}
}
}
smartSingleton.afterSingletonsInstantiated();
org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration.SameManagementContextConfiguration implements SmartInitializingSingleton
org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration.DifferentManagementContextConfiguration implements SmartInitializingSingleton
以DifferentManangentContextConfiguration为例
@Override
public void afterSingletonsInstantiated() {
if (this.applicationContext instanceof WebServerApplicationContext
&& ((WebServerApplicationContext) this.applicationContext)
.getWebServer() != null) {
ConfigurableWebServerApplicationContext managementContext = this.managementContextFactory
.createManagementContext(this.applicationContext,
EnableChildManagementContextConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
managementContext.setServerNamespace("management");
managementContext.setId(this.applicationContext.getId() + ":management");
setClassLoaderIfPossible(managementContext);
CloseManagementContextListener.addIfPossible(this.applicationContext,
managementContext);
//这里将会重新定义ApplicationContext
managementContext.refresh();
}
else {
logger.warn("Could not start embedded management container on "
+ "different port (management endpoints are still available "
+ "through JMX)");
}
}
SpringBoot中关于Accesslog的配置(以内置sever为undertow为例)
在application.properteies中server.undertow.accesslog进行相关配置。
这里记录一个遇到的问题
就是accesslog.pattern中%D(响应duration)没有生效。
原因在于默认情况下,这个是不生效的。
io.undertow.server.protocol.http.HttpReadListener#handleEventWithNoRunningRequest(final ConduitStreamSourceChannel channel)
HttpReadListener(final HttpServerConnection connection, final HttpRequestParser parser, ConnectorStatisticsImpl connectorStatistics) {
this.connection = connection;
this.parser = parser;
this.connectorStatistics = connectorStatistics;
this.maxRequestSize = connection.getUndertowOptions().get(UndertowOptions.MAX_HEADER_SIZE, UndertowOptions.DEFAULT_MAX_HEADER_SIZE);
this.maxEntitySize = connection.getUndertowOptions().get(UndertowOptions.MAX_ENTITY_SIZE, UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
this.recordRequestStartTime = connection.getUndertowOptions().get(UndertowOptions.RECORD_REQUEST_START_TIME, false);
this.requireHostHeader = connection.getUndertowOptions().get(UndertowOptions.REQUIRE_HOST_HTTP11, true);
this.allowUnknownProtocols = connection.getUndertowOptions().get(UndertowOptions.ALLOW_UNKNOWN_PROTOCOLS, false);
int requestParseTimeout = connection.getUndertowOptions().get(UndertowOptions.REQUEST_PARSE_TIMEOUT, -1);
int requestIdleTimeout = connection.getUndertowOptions().get(UndertowOptions.NO_REQUEST_TIMEOUT, -1);
if(requestIdleTimeout < 0 && requestParseTimeout < 0) {
this.parseTimeoutUpdater = null;
} else {
this.parseTimeoutUpdater = new ParseTimeoutUpdater(connection, requestParseTimeout, requestIdleTimeout);
connection.addCloseListener(parseTimeoutUpdater);
}
state = new ParseState(connection.getUndertowOptions().get(UndertowOptions.HTTP_HEADERS_CACHE_SIZE, UndertowOptions.DEFAULT_HTTP_HEADERS_CACHE_SIZE));
}
if (recordRequestStartTime) {
Connectors.setRequestStartTime(httpServerExchange);
}
io.undertow.attribute.ResponseTimeAttribute#readAttribute(HttpServerExchange exchange)
@Override
public String readAttribute(HttpServerExchange exchange) {
long requestStartTime = exchange.getRequestStartTime();
if(requestStartTime == -1) {
return null;
}
...
}
如何让%D生效?
方法一
使用一个BeanPostProcessor,重写public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException方法,当bean instanceof UndertowServletWebServerFactory成立时,调用factory.addBuilderCustomizers(UndertowBuilderCustomizer... customizers)
重写UndertowBuilderCustomizer的方法: builder.setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME,Boolean.TRUE).
最终会在org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory#createBuilder(int port)完成调用
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
DeploymentManager manager = createDeploymentManager(initializers);
int port = getPort();
Builder builder = createBuilder(port);
return getUndertowWebServer(builder, manager, port);
}
private Builder createBuilder(int port) {
Builder builder = Undertow.builder();
if (this.bufferSize != null) {
builder.setBufferSize(this.bufferSize);
}
if (this.ioThreads != null) {
builder.setIoThreads(this.ioThreads);
}
if (this.workerThreads != null) {
builder.setWorkerThreads(this.workerThreads);
}
if (this.directBuffers != null) {
builder.setDirectBuffers(this.directBuffers);
}
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(builder);
}
else {
builder.addHttpListener(port, getListenAddress());
}
for (UndertowBuilderCustomizer customizer : this.builderCustomizers) {
customizer.customize(builder);
}
return builder;
}
方法二 实现WebServerFactoryCustomizer接口,重写customize(UndertowServletWebServerFactory factory)方法
org.springframework.boot.web.server。WebServerFactoryCustomizerBeanPostProcessor#postProcessBeforeInitialization(WebServerFactory webServerFactory)最终会调用
@SuppressWarnings("unchecked")
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe
.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
.invoke((customizer) -> customizer.customize(webServerFactory));
}
private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
return (Collection) this.beanFactory
.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
}
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.class
@Override
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessBeforeInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
彩蛋org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextFactory
private void registerServletWebServerFactory(ApplicationContext parent,
AnnotationConfigServletWebServerApplicationContext childContext) {
try {
ConfigurableListableBeanFactory beanFactory = childContext.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
registry.registerBeanDefinition("ServletWebServerFactory",
new RootBeanDefinition(
determineServletWebServerFactoryClass(parent)));
}
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore and assume auto-configuration
}
}
注意 这里只是把servletWebServerFactory继承过来了